use crate::document::DocumentManager;
use codegraph_core::{GraphSystem, Result as CoreResult};
use tower_lsp::lsp_types::{Location, Position, Range, ReferenceParams, Url};
use std::sync::Arc;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use tokio::sync::{RwLock, Mutex};
use tracing::{debug, warn};
use regex::Regex;

/// Cache entry for reference locations
#[derive(Debug, Clone)]
struct ReferencesCacheEntry {
    locations: Vec<Location>,
    timestamp: Instant,
    content_hash: u64,
}

/// References cache with TTL
struct ReferencesCache {
    entries: HashMap<String, ReferencesCacheEntry>,
    ttl: Duration,
}

impl ReferencesCache {
    fn new(ttl: Duration) -> Self {
        Self {
            entries: HashMap::new(),
            ttl,
        }
    }

    fn get(&self, key: &str, content_hash: u64) -> Option<Vec<Location>> {
        if let Some(entry) = self.entries.get(key) {
            if entry.timestamp.elapsed() < self.ttl && entry.content_hash == content_hash {
                return Some(entry.locations.clone());
            }
        }
        None
    }

    fn insert(&mut self, key: String, locations: Vec<Location>, content_hash: u64) {
        let entry = ReferencesCacheEntry {
            locations,
            timestamp: Instant::now(),
            content_hash,
        };
        self.entries.insert(key, entry);
    }

    fn clear(&mut self) {
        self.entries.clear();
    }
}

/// Enhanced references provider with cross-file search and intelligent caching
pub struct ReferencesProvider {
    document_manager: Arc<DocumentManager>,
    graph_system: Arc<RwLock<Option<GraphSystem>>>,
    cache: Arc<Mutex<ReferencesCache>>,
    // Pre-compiled regex patterns for better performance
    reference_patterns: HashMap<String, Regex>,
}

impl ReferencesProvider {
    pub fn new(
        document_manager: Arc<DocumentManager>,
        graph_system: Arc<RwLock<Option<GraphSystem>>>,
    ) -> Self {
        let cache = Arc::new(Mutex::new(ReferencesCache::new(Duration::from_secs(90)))); // 90 second TTL

        // Pre-compile reference patterns for different languages
        let mut reference_patterns = HashMap::new();

        // Word boundary patterns for finding references
        reference_patterns.insert(
            "python".to_string(),
            Regex::new(r"\b(\w+)\b").unwrap(),
        );
        reference_patterns.insert(
            "javascript".to_string(),
            Regex::new(r"\b(\w+)\b").unwrap(),
        );
        reference_patterns.insert(
            "typescript".to_string(),
            Regex::new(r"\b(\w+)\b").unwrap(),
        );
        reference_patterns.insert(
            "rust".to_string(),
            Regex::new(r"\b(\w+)\b").unwrap(),
        );

        Self {
            document_manager,
            graph_system,
            cache,
            reference_patterns,
        }
    }

    pub async fn provide_references(
        &self,
        params: &ReferenceParams,
    ) -> CoreResult<Option<Vec<Location>>> {
        let start_time = Instant::now();
        let uri = &params.text_document_position.text_document.uri;
        let position = params.text_document_position.position;
        let include_declaration = params.context.include_declaration;

        debug!("Providing references for {} at {:?}", uri, position);

        // Get document
        let document = match self.document_manager.get_document(uri) {
            Some(doc) => doc,
            None => {
                warn!("Document not found for references: {}", uri);
                return Ok(None);
            }
        };

        let (language, word, content, content_hash) = {
            let doc = document.read().await;
            let language = match &doc.language {
                Some(lang) => lang.clone(),
                None => {
                    debug!("No language detected for references");
                    return Ok(None);
                }
            };

            // Get the word at the cursor position
            let word = match doc.get_word_at_position(position) {
                Ok(Some(word)) => word,
                Ok(None) => return Ok(None),
                Err(e) => {
                    warn!("Failed to get word at position: {}", e);
                    return Ok(None);
                }
            };

            let content = doc.content.clone();
            let content_hash = self.compute_content_hash(&content);
            (language, word, content, content_hash)
        };

        debug!("References word: '{}'", word);

        // Check cache first
        let cache_key = format!("{}:{}:{}:{}:{}", uri, position.line, position.character, word, include_declaration);
        {
            let cache = self.cache.lock().await;
            if let Some(cached_locations) = cache.get(&cache_key, content_hash) {
                debug!(
                    "Cache hit for references '{}' ({}ms)",
                    word,
                    start_time.elapsed().as_millis()
                );
                return Ok(Some(cached_locations));
            }
        }

        // Find all references
        let mut locations = self.find_references(&word, &language, &content, uri).await?;

        // Include declaration if requested
        if include_declaration {
            if let Some(declaration) = self.find_declaration(&word, &language, &content, uri) {
                // Add declaration at the beginning if not already present
                if !locations.iter().any(|loc| loc.range == declaration.range && loc.uri == declaration.uri) {
                    locations.insert(0, declaration);
                }
            }
        }

        // Cache the results
        {
            let mut cache = self.cache.lock().await;
            cache.insert(cache_key, locations.clone(), content_hash);
        }

        let elapsed = start_time.elapsed();
        debug!(
            "References provided for '{}' in {}ms ({} locations)",
            word,
            elapsed.as_millis(),
            locations.len()
        );

        if locations.is_empty() {
            Ok(None)
        } else {
            Ok(Some(locations))
        }
    }

    /// Compute a simple hash for content to use in caching
    fn compute_content_hash(&self, content: &str) -> u64 {
        use std::collections::hash_map::DefaultHasher;
        use std::hash::{Hash, Hasher};

        let mut hasher = DefaultHasher::new();
        content.hash(&mut hasher);
        hasher.finish()
    }

    async fn find_references(
        &self,
        word: &str,
        language: &str,
        content: &str,
        uri: &Url,
    ) -> CoreResult<Vec<Location>> {
        let mut locations = Vec::new();

        // Search in current document using enhanced patterns
        locations.extend(self.find_references_with_regex(word, language, content, uri));

        // Search in other open documents
        let other_locations = self.find_references_in_other_documents(word, language, uri).await?;
        locations.extend(other_locations);

        // Remove duplicates and sort by location
        locations.sort_by(|a, b| {
            match a.uri.cmp(&b.uri) {
                std::cmp::Ordering::Equal => {
                    match a.range.start.line.cmp(&b.range.start.line) {
                        std::cmp::Ordering::Equal => a.range.start.character.cmp(&b.range.start.character),
                        other => other,
                    }
                }
                other => other,
            }
        });
        locations.dedup_by(|a, b| a.uri == b.uri && a.range == b.range);

        Ok(locations)
    }

    fn find_references_in_content(
        &self,
        word: &str,
        language: &str,
        content: &str,
        uri: &Url,
    ) -> Vec<Location> {
        let mut locations = Vec::new();
        let lines: Vec<&str> = content.lines().collect();

        for (line_num, line) in lines.iter().enumerate() {
            // Find all occurrences of the word in this line
            let mut start_pos = 0;
            while let Some(pos) = line[start_pos..].find(word) {
                let actual_pos = start_pos + pos;
                
                // Check if this is a valid reference (not part of another word)
                if self.is_valid_reference(line, actual_pos, word) {
                    // Skip if this is a definition (not a reference)
                    if !self.is_definition_line(line, word, language) {
                        let range = Range::new(
                            Position::new(line_num as u32, actual_pos as u32),
                            Position::new(line_num as u32, (actual_pos + word.len()) as u32),
                        );
                        locations.push(Location::new(uri.clone(), range));
                    }
                }
                
                start_pos = actual_pos + 1;
            }
        }

        locations
    }

    fn find_declaration(
        &self,
        word: &str,
        language: &str,
        content: &str,
        uri: &Url,
    ) -> Option<Location> {
        let lines: Vec<&str> = content.lines().collect();

        for (line_num, line) in lines.iter().enumerate() {
            if self.is_definition_line(line, word, language) {
                if let Some(pos) = line.find(word) {
                    let range = Range::new(
                        Position::new(line_num as u32, pos as u32),
                        Position::new(line_num as u32, (pos + word.len()) as u32),
                    );
                    return Some(Location::new(uri.clone(), range));
                }
            }
        }

        None
    }

    fn is_valid_reference(&self, line: &str, pos: usize, word: &str) -> bool {
        // Check if the word is surrounded by word boundaries
        let before_char = if pos > 0 {
            line.chars().nth(pos - 1)
        } else {
            None
        };

        let after_char = line.chars().nth(pos + word.len());

        let is_word_boundary_before = before_char.map_or(true, |c| !c.is_alphanumeric() && c != '_');
        let is_word_boundary_after = after_char.map_or(true, |c| !c.is_alphanumeric() && c != '_');

        is_word_boundary_before && is_word_boundary_after
    }

    fn is_definition_line(&self, line: &str, word: &str, language: &str) -> bool {
        let trimmed = line.trim();
        
        // Skip comments
        if trimmed.starts_with('#') || trimmed.starts_with("//") {
            return false;
        }

        match language {
            "python" => {
                // Function definition
                (trimmed.starts_with("def ") && trimmed.contains(&format!("{}(", word))) ||
                // Class definition
                (trimmed.starts_with("class ") && (
                    trimmed.contains(&format!("{} ", word)) ||
                    trimmed.contains(&format!("{}(", word)) ||
                    trimmed.contains(&format!("{}:", word))
                )) ||
                // Variable assignment (first occurrence)
                (trimmed.contains(&format!("{} =", word)) && !trimmed.contains("=="))
            }
            "javascript" | "typescript" => {
                // Function definition
                (trimmed.starts_with("function ") && trimmed.contains(&format!("{}(", word))) ||
                // Arrow function
                ((trimmed.starts_with("const ") || trimmed.starts_with("let ")) && 
                 trimmed.contains(&format!("{} =", word)) && trimmed.contains("=>")) ||
                // Class definition
                (trimmed.starts_with("class ") && trimmed.contains(&format!("{} ", word))) ||
                // Variable declaration
                ((trimmed.starts_with("const ") || trimmed.starts_with("let ") || trimmed.starts_with("var ")) &&
                 trimmed.contains(&format!("{} =", word)))
            }
            "rust" => {
                // Function definition
                (trimmed.starts_with("fn ") && trimmed.contains(&format!("{}(", word))) ||
                // Struct/enum definition
                ((trimmed.starts_with("struct ") || trimmed.starts_with("enum ")) &&
                 trimmed.contains(&format!("{} ", word))) ||
                // Variable binding
                (trimmed.starts_with("let ") && trimmed.contains(&format!("{} =", word)))
            }
            _ => {
                // Generic pattern for other languages
                trimmed.contains(&format!("{} =", word)) && !trimmed.contains("==")
            }
        }
    }

    /// Enhanced reference finding using regex patterns
    fn find_references_with_regex(
        &self,
        word: &str,
        language: &str,
        content: &str,
        uri: &Url,
    ) -> Vec<Location> {
        let mut locations = Vec::new();

        if let Some(pattern) = self.reference_patterns.get(language) {
            for (line_num, line) in content.lines().enumerate() {
                for captures in pattern.captures_iter(line) {
                    if let Some(matched) = captures.get(1) {
                        if matched.as_str() == word {
                            // Check if this is not a definition (simple heuristic)
                            if !self.is_likely_definition(line, word, language) {
                                let start_col = matched.start();
                                let end_col = matched.end();
                                let range = Range::new(
                                    Position::new(line_num as u32, start_col as u32),
                                    Position::new(line_num as u32, end_col as u32),
                                );
                                locations.push(Location::new(uri.clone(), range));
                            }
                        }
                    }
                }
            }
        }

        // Fallback to the original method if regex fails
        if locations.is_empty() {
            locations = self.find_references_in_content(word, language, content, uri);
        }

        locations
    }

    /// Find references in other open documents
    async fn find_references_in_other_documents(
        &self,
        word: &str,
        language: &str,
        current_uri: &Url,
    ) -> CoreResult<Vec<Location>> {
        let mut locations = Vec::new();

        // Get all open documents
        let document_uris = self.document_manager.list_documents();
        let documents: Vec<_> = document_uris.iter()
            .filter_map(|uri| self.document_manager.get_document(uri).map(|doc| (uri.clone(), doc)))
            .collect();

        for (uri, document) in documents {
            // Skip the current document
            if uri == *current_uri {
                continue;
            }

            let doc = document.read().await;

            // Only search in documents of the same language or related languages
            if let Some(doc_language) = &doc.language {
                if self.is_related_language(language, doc_language) {
                    let doc_references = self.find_references_with_regex(word, doc_language, &doc.content, &uri);
                    locations.extend(doc_references);
                }
            }
        }

        Ok(locations)
    }

    /// Simple heuristic to check if a line contains a definition
    fn is_likely_definition(&self, line: &str, word: &str, language: &str) -> bool {
        let trimmed = line.trim();

        match language {
            "python" => {
                trimmed.starts_with("def ") && trimmed.contains(&format!("{}(", word)) ||
                trimmed.starts_with("class ") && trimmed.contains(&format!("{} ", word)) ||
                trimmed.contains(&format!("{} =", word)) && !trimmed.contains("==")
            }
            "javascript" | "typescript" => {
                trimmed.starts_with("function ") && trimmed.contains(&format!("{}(", word)) ||
                trimmed.starts_with("class ") && trimmed.contains(&format!("{} ", word)) ||
                (trimmed.starts_with("const ") || trimmed.starts_with("let ") || trimmed.starts_with("var ")) &&
                trimmed.contains(&format!("{} =", word))
            }
            "rust" => {
                trimmed.starts_with("fn ") && trimmed.contains(&format!("{}(", word)) ||
                (trimmed.starts_with("struct ") || trimmed.starts_with("enum ")) && trimmed.contains(&format!("{} ", word)) ||
                trimmed.starts_with("let ") && trimmed.contains(&format!("{} =", word))
            }
            _ => false,
        }
    }

    /// Check if two languages are related (e.g., TypeScript and JavaScript)
    fn is_related_language(&self, lang1: &str, lang2: &str) -> bool {
        if lang1 == lang2 {
            return true;
        }

        match (lang1, lang2) {
            ("javascript", "typescript") | ("typescript", "javascript") => true,
            ("python", "python3") | ("python3", "python") => true,
            _ => false,
        }
    }
}
